iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

(Token)倒楣鬼程式碼

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  constructor(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;
  }

  function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
  }

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
  }
}

通關條件

玩家起始擁有 20 個代幣,而玩家只要能夠取得任一數量之額外的代幣(balance > 20),即可通關

先備知識 Overflow

Solidity 內並不像 Python 一樣內建大數運算,換言之,Solidity 的運算是會溢位 (Overflow) 的,老樣子,來看看下面的測試碼吧

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract overflow {
    uint256 b = 0;
    function _overflow() public view returns(uint256) {
        return b - 1;
    }
}


Solidity uint256 的計算範圍為 0 ~ (2^256)-1,負數或超過範圍的運算都會使其溢位進而得到預料之外的結果,了解了這關的核心之後,接著就進入程式碼分析吧。

程式碼

function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
}

這關的程式碼和上一關一樣並不長,所以我們只要重點看這個帶有漏洞的 transfer 就可以了,可以發現裡面做運算時沒有預防 Overflow 的機制,所以我們可以很輕鬆的通過 require 的審查進而取得額外的代幣;player 一開始有 20 個代幣,我們可以執行 transfer 函數,讓 player 轉錢給一個隨意地址,而傳送數量則訂為 21, balances[msg.sender] - _value 將會溢位成為 2^256-1,通過 require 後,自身的 balances 則會扣除 21 顆 Token,同樣會溢位成為 2^256-1,空地址會獲得 21 個代幣,OK,那就立刻來動手實作吧。

通關

我們需要一個空地址來做為轉帳對象,這邊就直接拿關卡作者的 address 來充當空地址了 XD

player2 = "0x31a3801499618d3c4b0225b9e06e228d4795b55d"

接著執行 transfer

await contract.transfer(player2, 21)



◕_◕ ◕_◕ ◕_◕ ◕_◕

保護

剛剛有提到,Solidity 在運算時含有溢位的問題,而預防方式有兩個

  • 使用 SafeMath Library
  • Solidity version >= 0.8.0
    SafeMath 是由 Openzeppelin 團隊撰寫的 Solidity Library,功能為測試運算是否溢位,可以在他們的 github 上找到程式碼,而 Solidity 也在 0.8.0 版本後正式納入了溢位運算的檢測,可以看看下圖兩個測試碼的結果。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract overflow {
    uint256 b = 0;
    function _overflow() public view returns(uint256) {
        return b - 1;
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract overflow {
    uint256 b = 0;
    function _overflow() public view returns(uint256) {
        return b - 1;
    }
}


所以只要將開發版本改為使用 0.8.0 以上,就不用擔心溢位問題啦,但是溢位運算檢測也是一個步驟,在區塊鏈系統上,每個步驟都是要算錢的,因此 Solidity 多出了一個新的語法 unchecked 這個語法是當你非常確定某個部分不可能產生溢位,則可以使用 unchecked 來省去溢位運算檢測,藉此來降低 gas fee。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract overflow {
    uint256 b = 0;
    function _overflow() public view returns(uint256) {
        unchecked {
            return b - 1;
        }
    }
}


可以從圖片上看到,編譯器並沒有執行檢測意味的動作。

reference

https://docs.soliditylang.org/en/v0.8.13/080-breaking-changes.html


上一篇
Day 7 - Telephone
下一篇
Day 9 - Delegatecall
系列文
智能合約漏洞演練 - Ethernaut18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言